// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_chromeos.h"

#include <limits>
#include <memory>
#include <set>
#include <string>
#include <utility>

#include "base/bind.h"
#include "base/containers/contains.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/task_environment.h"
#include "components/account_manager_core/account.h"
#include "components/account_manager_core/account_manager_facade.h"
#include "components/account_manager_core/account_manager_facade_impl.h"
#include "components/account_manager_core/chromeos/account_manager.h"
#include "components/account_manager_core/chromeos/account_manager_mojo_service.h"
#include "components/account_manager_core/mock_account_manager_facade.h"
#include "components/signin/internal/identity_manager/account_tracker_service.h"
#include "components/signin/internal/identity_manager/profile_oauth2_token_service_observer.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/signin/public/base/test_signin_client.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "google_apis/gaia/gaia_urls.h"
#include "google_apis/gaia/oauth2_access_token_consumer.h"
#include "google_apis/gaia/oauth2_access_token_fetcher.h"
#include "google_apis/gaia/oauth2_access_token_manager_test_util.h"
#include "services/network/test/test_network_connection_tracker.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace signin {

namespace {

using ::account_manager::AccountManager;
using ::account_manager::AccountManagerFacade;

constexpr char kGaiaId[] = "gaia-id";
constexpr char kGaiaToken[] = "gaia-token";
constexpr char kUserEmail[] = "user@gmail.com";

class AccessTokenConsumer : public OAuth2AccessTokenConsumer {
 public:
  AccessTokenConsumer() = default;

  AccessTokenConsumer(const AccessTokenConsumer&) = delete;
  AccessTokenConsumer& operator=(const AccessTokenConsumer&) = delete;

  ~AccessTokenConsumer() override = default;

  void OnGetTokenSuccess(const TokenResponse& token_response) override {
    ++num_access_token_fetch_success_;
  }

  void OnGetTokenFailure(const GoogleServiceAuthError& error) override {
    ++num_access_token_fetch_failure_;
  }

  std::string GetConsumerName() const override {
    return "profile_oauth2_token_service_delegate_chromeos_unittest";
  }

  int num_access_token_fetch_success_ = 0;
  int num_access_token_fetch_failure_ = 0;
};

class TestOAuth2TokenServiceObserver
    : public ProfileOAuth2TokenServiceObserver {
 public:
  // |delegate| is a non-owning pointer to an
  // |ProfileOAuth2TokenServiceDelegate| that MUST outlive |this| instance.
  explicit TestOAuth2TokenServiceObserver(
      ProfileOAuth2TokenServiceDelegate* delegate)
      : delegate_(delegate) {
    delegate_->AddObserver(this);
  }

  ~TestOAuth2TokenServiceObserver() override {
    delegate_->RemoveObserver(this);
  }

  void StartBatchChanges() {
    EXPECT_FALSE(is_inside_batch_);
    is_inside_batch_ = true;

    // Start a new batch
    batch_change_records_.emplace_back(std::vector<CoreAccountId>());
  }

  void OnEndBatchChanges() override {
    EXPECT_TRUE(is_inside_batch_);
    is_inside_batch_ = false;
  }

  void OnRefreshTokenAvailable(const CoreAccountId& account_id) override {
    if (!is_inside_batch_)
      StartBatchChanges();

    // We should not be seeing any cached errors for a freshly updated account,
    // except when they have been generated by us (i.e.
    // CREDENTIALS_REJECTED_BY_CLIENT).
    const GoogleServiceAuthError error = delegate_->GetAuthError(account_id);
    EXPECT_TRUE((error == GoogleServiceAuthError::AuthErrorNone()) ||
                (error.state() ==
                     GoogleServiceAuthError::State::INVALID_GAIA_CREDENTIALS &&
                 error.GetInvalidGaiaCredentialsReason() ==
                     GoogleServiceAuthError::InvalidGaiaCredentialsReason::
                         CREDENTIALS_REJECTED_BY_CLIENT));

    account_ids_.insert(account_id);

    // Record the |account_id| in the last batch.
    batch_change_records_.rbegin()->emplace_back(account_id);
  }

  void OnRefreshTokensLoaded() override { refresh_tokens_loaded_ = true; }

  void OnRefreshTokenRevoked(const CoreAccountId& account_id) override {
    if (!is_inside_batch_)
      StartBatchChanges();

    account_ids_.erase(account_id);
    // Record the |account_id| in the last batch.
    batch_change_records_.rbegin()->emplace_back(account_id);
  }

  void OnAuthErrorChanged(const CoreAccountId& account_id,
                          const GoogleServiceAuthError& auth_error) override {
    last_err_account_id_ = account_id;
    last_err_ = auth_error;
    on_auth_error_changed_calls++;
  }

  int on_auth_error_changed_calls = 0;

  CoreAccountId last_err_account_id_;
  GoogleServiceAuthError last_err_;
  std::set<CoreAccountId> account_ids_;
  bool is_inside_batch_ = false;
  bool refresh_tokens_loaded_ = false;

  // Records batch changes for later verification. Each index of this vector
  // represents a batch change. Each batch change is a vector of account ids for
  // which |OnRefreshTokenAvailable| is called.
  std::vector<std::vector<CoreAccountId>> batch_change_records_;

  // Non-owning pointer.
  ProfileOAuth2TokenServiceDelegate* const delegate_;
};

class MockProfileOAuth2TokenServiceObserver
    : public ProfileOAuth2TokenServiceObserver {
 public:
  explicit MockProfileOAuth2TokenServiceObserver(
      ProfileOAuth2TokenServiceDelegate* delegate)
      : delegate_(delegate) {
    delegate_->AddObserver(this);
  }

  ~MockProfileOAuth2TokenServiceObserver() override {
    delegate_->RemoveObserver(this);
  }

  MockProfileOAuth2TokenServiceObserver(
      const MockProfileOAuth2TokenServiceObserver&) = delete;
  MockProfileOAuth2TokenServiceObserver& operator=(
      const MockProfileOAuth2TokenServiceObserver&) = delete;

  MOCK_METHOD(void, OnRefreshTokensLoaded, (), (override));
  MOCK_METHOD(void,
              OnRefreshTokenAvailable,
              (const CoreAccountId&),
              (override));
  MOCK_METHOD(void, OnRefreshTokenRevoked, (const CoreAccountId&), (override));

 private:
  ProfileOAuth2TokenServiceDelegate* const delegate_;
};

}  // namespace

class ProfileOAuth2TokenServiceDelegateChromeOSTest : public testing::Test {
 public:
  ProfileOAuth2TokenServiceDelegateChromeOSTest() {}

  ProfileOAuth2TokenServiceDelegateChromeOSTest(
      const ProfileOAuth2TokenServiceDelegateChromeOSTest&) = delete;
  ProfileOAuth2TokenServiceDelegateChromeOSTest& operator=(
      const ProfileOAuth2TokenServiceDelegateChromeOSTest&) = delete;

  ~ProfileOAuth2TokenServiceDelegateChromeOSTest() override = default;

 protected:
  void SetUp() override {
    ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir());
    AccountTrackerService::RegisterPrefs(pref_service_.registry());
    AccountManager::RegisterPrefs(pref_service_.registry());

    client_ = std::make_unique<TestSigninClient>(&pref_service_);
    account_manager_.Initialize(tmp_dir_.GetPath(),
                                client_->GetURLLoaderFactory(),
                                immediate_callback_runner_);
    account_manager_.SetPrefService(&pref_service_);
    task_environment_.RunUntilIdle();

    account_manager_mojo_service_ =
        std::make_unique<crosapi::AccountManagerMojoService>(&account_manager_);
    account_manager_facade_ =
        CreateAccountManagerFacade(account_manager_mojo_service_.get());

    account_tracker_service_.Initialize(&pref_service_, base::FilePath());

    account_info_ = CreateAccountInfoTestFixture(kGaiaId, kUserEmail);
    account_tracker_service_.SeedAccountInfo(account_info_);
    delegate_ = std::make_unique<ProfileOAuth2TokenServiceDelegateChromeOS>(
        &account_tracker_service_,
        network::TestNetworkConnectionTracker::GetInstance(),
        account_manager_facade_.get(), true /* is_regular_profile */);

    LoadCredentialsAndWaitForCompletion(
        account_info_.account_id /* primary_account_id */);
  }

  account_manager::AccountKey gaia_account_key() const {
    return {account_info_.gaia, account_manager::AccountType::kGaia};
  }

  account_manager::AccountKey ad_account_key() const {
    return {"object-guid", account_manager::AccountType::kActiveDirectory};
  }

  AccountInfo CreateAccountInfoTestFixture(const std::string& gaia_id,
                                           const std::string& email) {
    AccountInfo account_info;

    account_info.gaia = gaia_id;
    account_info.email = email;
    account_info.full_name = "name";
    account_info.given_name = "name";
    account_info.hosted_domain = "example.com";
    account_info.locale = "en";
    account_info.picture_url = "https://example.com";
    account_info.account_id = account_tracker_service_.PickAccountIdForAccount(
        account_info.gaia, account_info.email);

    // Cannot use |ASSERT_TRUE| due to a |void| return type in an |ASSERT_TRUE|
    // branch.
    EXPECT_TRUE(account_info.IsValid());

    return account_info;
  }

  void AddSuccessfulOAuthTokenResponse() {
    client_->GetTestURLLoaderFactory()->AddResponse(
        GaiaUrls::GetInstance()->oauth2_token_url().spec(),
        GetValidTokenResponse("token", 3600));
  }

  void LoadCredentialsAndWaitForCompletion(
      const CoreAccountId& primary_account_id) {
    MockProfileOAuth2TokenServiceObserver observer(delegate_.get());
    base::RunLoop run_loop;
    EXPECT_CALL(observer, OnRefreshTokensLoaded())
        .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
    delegate_->LoadCredentials(primary_account_id);
    run_loop.Run();
  }

  void UpsertAccountAndWaitForCompletion(
      const ::account_manager::AccountKey& account_key,
      const std::string& raw_email,
      const std::string& token) {
    ASSERT_EQ(account_key.account_type(), account_manager::AccountType::kGaia);

    // `ProfileOAuth2TokenServiceDelegateChromeOS` asynchronously obtains error
    // statuses for Gaia accounts, so we have to wait for a notification from
    // the delegate itself here.
    MockProfileOAuth2TokenServiceObserver observer(delegate_.get());
    base::RunLoop run_loop;
    EXPECT_CALL(observer, OnRefreshTokenAvailable(testing::_))
        .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
    account_manager_.UpsertAccount(account_key, raw_email, token);
    run_loop.Run();
  }

  void RemoveAccountAndWaitForCompletion(
      const ::account_manager::AccountKey& account_key) {
    ASSERT_EQ(account_key.account_type(), account_manager::AccountType::kGaia);
    MockProfileOAuth2TokenServiceObserver observer(delegate_.get());
    base::RunLoop run_loop;
    EXPECT_CALL(observer, OnRefreshTokenRevoked(testing::_))
        .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
    account_manager_.RemoveAccount(account_key);
    run_loop.Run();
  }

  void UpsertActiveDirectoryAccountAndWaitForCompletion(
      const ::account_manager::AccountKey& account_key,
      const std::string& raw_email,
      const std::string& token) {
    ASSERT_EQ(account_key.account_type(),
              account_manager::AccountType::kActiveDirectory);
    account_manager::MockAccountManagerFacadeObserver observer;
    account_manager_facade_->AddObserver(&observer);

    base::RunLoop run_loop;
    EXPECT_CALL(observer, OnAccountUpserted(testing::_))
        .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
    account_manager_.UpsertAccount(account_key, raw_email, token);
    run_loop.Run();

    account_manager_facade_->RemoveObserver(&observer);
  }

  void RemoveActiveDirectoryAccountAndWaitForCompletion(
      const ::account_manager::AccountKey& account_key) {
    ASSERT_EQ(account_key.account_type(),
              account_manager::AccountType::kActiveDirectory);
    account_manager::MockAccountManagerFacadeObserver observer;
    account_manager_facade_->AddObserver(&observer);

    base::RunLoop run_loop;
    EXPECT_CALL(observer, OnAccountRemoved(testing::_))
        .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
    account_manager_.RemoveAccount(account_key);
    run_loop.Run();

    account_manager_facade_->RemoveObserver(&observer);
  }

  std::unique_ptr<AccountManagerFacade> CreateAccountManagerFacade(
      crosapi::AccountManagerMojoService* account_manager_mojo_service) {
    DCHECK(account_manager_mojo_service);
    mojo::Remote<crosapi::mojom::AccountManager> remote;
    account_manager_mojo_service->BindReceiver(
        remote.BindNewPipeAndPassReceiver());
    return std::make_unique<account_manager::AccountManagerFacadeImpl>(
        std::move(remote),
        /*remote_version=*/std::numeric_limits<uint32_t>::max(),
        /*account_manager_for_tests=*/nullptr);
  }

  base::test::TaskEnvironment task_environment_;

  base::ScopedTempDir tmp_dir_;
  AccountInfo account_info_;
  AccountTrackerService account_tracker_service_;
  AccountManager account_manager_;
  std::unique_ptr<crosapi::AccountManagerMojoService>
      account_manager_mojo_service_;
  std::unique_ptr<account_manager::AccountManagerFacade>
      account_manager_facade_;
  std::unique_ptr<ProfileOAuth2TokenServiceDelegateChromeOS> delegate_;
  AccountManager::DelayNetworkCallRunner immediate_callback_runner_ =
      base::BindRepeating(
          [](base::OnceClosure closure) -> void { std::move(closure).Run(); });
  sync_preferences::TestingPrefServiceSyncable pref_service_;
  std::unique_ptr<TestSigninClient> client_;
};

// Refresh tokens should load successfully for non-regular (Signin and Lock
// Screen) Profiles.
TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       RefreshTokensAreLoadedForNonRegularProfiles) {
  // Create an instance of Account Manager but do not
  // |AccountManager::Initialize| it. This mimics Signin and Lock Screen Profile
  // behaviour.
  AccountManager account_manager;

  auto delegate = std::make_unique<ProfileOAuth2TokenServiceDelegateChromeOS>(
      &account_tracker_service_,
      network::TestNetworkConnectionTracker::GetInstance(),
      account_manager_facade_.get(), false /* is_regular_profile */);
  TestOAuth2TokenServiceObserver observer(delegate.get());

  // Test that LoadCredentials works as expected.
  EXPECT_FALSE(observer.refresh_tokens_loaded_);
  delegate->LoadCredentials(CoreAccountId() /* primary_account_id */);
  EXPECT_TRUE(observer.refresh_tokens_loaded_);
  EXPECT_EQ(LoadCredentialsState::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS,
            delegate->load_credentials_state());
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       RefreshTokenIsAvailableReturnsTrueForValidGaiaTokens) {
  EXPECT_EQ(LoadCredentialsState::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS,
            delegate_->load_credentials_state());

  EXPECT_FALSE(delegate_->RefreshTokenIsAvailable(account_info_.account_id));
  EXPECT_FALSE(
      base::Contains(delegate_->GetAccounts(), account_info_.account_id));

  UpsertAccountAndWaitForCompletion(gaia_account_key(), kUserEmail, kGaiaToken);

  EXPECT_TRUE(delegate_->RefreshTokenIsAvailable(account_info_.account_id));
  EXPECT_TRUE(
      base::Contains(delegate_->GetAccounts(), account_info_.account_id));
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       RefreshTokenIsAvailableReturnsTrueForInvalidGaiaTokens) {
  EXPECT_EQ(LoadCredentialsState::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS,
            delegate_->load_credentials_state());

  EXPECT_FALSE(delegate_->RefreshTokenIsAvailable(account_info_.account_id));
  EXPECT_FALSE(
      base::Contains(delegate_->GetAccounts(), account_info_.account_id));

  UpsertAccountAndWaitForCompletion(gaia_account_key(), kUserEmail,
                                    AccountManager::kInvalidToken);

  EXPECT_TRUE(delegate_->RefreshTokenIsAvailable(account_info_.account_id));
  EXPECT_TRUE(
      base::Contains(delegate_->GetAccounts(), account_info_.account_id));
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       ObserversAreNotifiedOnAuthErrorChange) {
  TestOAuth2TokenServiceObserver observer(delegate_.get());
  auto error =
      GoogleServiceAuthError(GoogleServiceAuthError::State::SERVICE_ERROR);

  delegate_->UpdateAuthError(account_info_.account_id, error);
  EXPECT_EQ(error, delegate_->GetAuthError(account_info_.account_id));
  EXPECT_EQ(account_info_.account_id, observer.last_err_account_id_);
  EXPECT_EQ(error, observer.last_err_);
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       ObserversAreNotNotifiedIfErrorDidntChange) {
  TestOAuth2TokenServiceObserver observer(delegate_.get());
  auto error =
      GoogleServiceAuthError(GoogleServiceAuthError::State::SERVICE_ERROR);

  delegate_->UpdateAuthError(account_info_.account_id, error);
  EXPECT_EQ(1, observer.on_auth_error_changed_calls);
  delegate_->UpdateAuthError(account_info_.account_id, error);
  EXPECT_EQ(1, observer.on_auth_error_changed_calls);
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       ObserversAreNotifiedIfErrorDidChange) {
  TestOAuth2TokenServiceObserver observer(delegate_.get());
  delegate_->UpdateAuthError(
      account_info_.account_id,
      GoogleServiceAuthError(GoogleServiceAuthError::State::SERVICE_ERROR));
  EXPECT_EQ(1, observer.on_auth_error_changed_calls);

  delegate_->UpdateAuthError(
      account_info_.account_id,
      GoogleServiceAuthError(
          GoogleServiceAuthError::State::INVALID_GAIA_CREDENTIALS));
  EXPECT_EQ(2, observer.on_auth_error_changed_calls);
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       ObserversAreNotifiedOnCredentialsInsertion) {
  TestOAuth2TokenServiceObserver observer(delegate_.get());
  UpsertAccountAndWaitForCompletion(gaia_account_key(), account_info_.email,
                                    kGaiaToken);

  EXPECT_EQ(1UL, observer.account_ids_.size());
  EXPECT_EQ(account_info_.account_id, *observer.account_ids_.begin());
  EXPECT_EQ(account_info_.account_id, observer.last_err_account_id_);
  EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), observer.last_err_);
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       ObserversDoNotSeeCachedErrorsOnCredentialsUpdate) {
  UpsertAccountAndWaitForCompletion(gaia_account_key(), account_info_.email,
                                    kGaiaToken);

  // Deliberately add an error.
  auto error =
      GoogleServiceAuthError(GoogleServiceAuthError::State::SERVICE_ERROR);
  delegate_->UpdateAuthError(account_info_.account_id, error);

  // Update credentials. The delegate will check if see cached errors.
  UpsertAccountAndWaitForCompletion(gaia_account_key(), account_info_.email,
                                    "new-token");
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       ObserversDoNotSeeCachedErrorsOnAccountRemoval) {
  auto error =
      GoogleServiceAuthError(GoogleServiceAuthError::State::SERVICE_ERROR);
  UpsertAccountAndWaitForCompletion(gaia_account_key(), account_info_.email,
                                    kGaiaToken);
  // Deliberately add an error.
  delegate_->UpdateAuthError(account_info_.account_id, error);
  RemoveAccountAndWaitForCompletion(gaia_account_key());
  EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(),
            delegate_->GetAuthError(account_info_.account_id));
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       DummyTokensArePreEmptivelyRejected) {
  TestOAuth2TokenServiceObserver observer(delegate_.get());
  UpsertAccountAndWaitForCompletion(gaia_account_key(), account_info_.email,
                                    AccountManager::kInvalidToken);

  const GoogleServiceAuthError error =
      delegate_->GetAuthError(account_info_.account_id);
  EXPECT_EQ(GoogleServiceAuthError::State::INVALID_GAIA_CREDENTIALS,
            error.state());
  EXPECT_EQ(GoogleServiceAuthError::InvalidGaiaCredentialsReason::
                CREDENTIALS_REJECTED_BY_CLIENT,
            error.GetInvalidGaiaCredentialsReason());

  // Observer notification should also have notified about the same error.
  EXPECT_EQ(error, observer.last_err_);
  EXPECT_EQ(account_info_.account_id, observer.last_err_account_id_);
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       ObserversAreNotifiedOnCredentialsUpdate) {
  TestOAuth2TokenServiceObserver observer(delegate_.get());
  UpsertAccountAndWaitForCompletion(gaia_account_key(), account_info_.email,
                                    kGaiaToken);

  EXPECT_EQ(1UL, observer.account_ids_.size());
  EXPECT_EQ(account_info_.account_id, *observer.account_ids_.begin());
  EXPECT_EQ(account_info_.account_id, observer.last_err_account_id_);
  EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), observer.last_err_);
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       ObserversAreNotNotifiedIfCredentialsAreNotUpdated) {
  TestOAuth2TokenServiceObserver observer(delegate_.get());

  UpsertAccountAndWaitForCompletion(gaia_account_key(), account_info_.email,
                                    kGaiaToken);
  observer.account_ids_.clear();
  observer.last_err_account_id_ = CoreAccountId();
  // UpsertAccountAndWaitForCompletion can't be used here, as it uses an
  // observer to wait for completion. Observers aren't called in this flow, so
  // UpsertAccountAndWaitForCompletion would hang here.
  account_manager_.UpsertAccount(gaia_account_key(), account_info_.email,
                                 kGaiaToken);
  task_environment_.RunUntilIdle();

  EXPECT_TRUE(observer.account_ids_.empty());
  EXPECT_TRUE(observer.last_err_account_id_.empty());
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       BatchChangeObserversAreNotifiedOnCredentialsUpdate) {
  TestOAuth2TokenServiceObserver observer(delegate_.get());
  UpsertAccountAndWaitForCompletion(gaia_account_key(), account_info_.email,
                                    kGaiaToken);

  EXPECT_EQ(1UL, observer.batch_change_records_.size());
  EXPECT_EQ(1UL, observer.batch_change_records_[0].size());
  EXPECT_EQ(account_info_.account_id, observer.batch_change_records_[0][0]);
}

// If observers register themselves with |ProfileOAuth2TokenServiceDelegate|
// before |AccountManager| has been initialized, they should receive all the
// accounts stored in |AccountManager| in a single batch.
TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       BatchChangeObserversAreNotifiedOncePerBatch) {
  // Setup
  AccountInfo account1 = CreateAccountInfoTestFixture(
      "1" /* gaia_id */, "user1@example.com" /* email */);
  AccountInfo account2 = CreateAccountInfoTestFixture(
      "2" /* gaia_id */, "user2@example.com" /* email */);

  account_tracker_service_.SeedAccountInfo(account1);
  account_tracker_service_.SeedAccountInfo(account2);
  account_manager_.UpsertAccount(
      account_manager::AccountKey{account1.gaia,
                                  account_manager::AccountType::kGaia},
      "user1@example.com", "token1");
  account_manager_.UpsertAccount(
      account_manager::AccountKey{account2.gaia,
                                  account_manager::AccountType::kGaia},
      "user2@example.com", "token2");
  task_environment_.RunUntilIdle();

  AccountManager account_manager;
  // AccountManager will not be fully initialized until
  // |task_environment_.RunUntilIdle()| is called.
  account_manager.Initialize(tmp_dir_.GetPath(), client_->GetURLLoaderFactory(),
                             immediate_callback_runner_);
  account_manager.SetPrefService(&pref_service_);

  auto account_manager_mojo_service =
      std::make_unique<crosapi::AccountManagerMojoService>(&account_manager);
  auto account_manager_facade =
      CreateAccountManagerFacade(account_manager_mojo_service.get());

  // Register callbacks before AccountManager has been fully initialized.
  auto delegate = std::make_unique<ProfileOAuth2TokenServiceDelegateChromeOS>(
      &account_tracker_service_,
      network::TestNetworkConnectionTracker::GetInstance(),
      account_manager_facade.get(), true /* is_regular_profile */);
  delegate->LoadCredentials(account1.account_id /* primary_account_id */);
  TestOAuth2TokenServiceObserver observer(delegate.get());
  // Wait until AccountManager is fully initialized.
  task_environment_.RunUntilIdle();

  // Tests

  // The observer should receive at least one batch change callback: batch of
  // all accounts stored in AccountManager: because of the delegate's
  // invocation of |AccountManagerFacade::GetAccounts| in |LoadCredentials|.
  EXPECT_FALSE(observer.batch_change_records_.empty());
  const std::vector<CoreAccountId>& first_batch =
      observer.batch_change_records_[0];
  EXPECT_EQ(2UL, first_batch.size());
  EXPECT_TRUE(base::Contains(first_batch, account1.account_id));
  EXPECT_TRUE(base::Contains(first_batch, account2.account_id));
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       GetAccountsShouldNotReturnAdAccounts) {
  EXPECT_TRUE(delegate_->GetAccounts().empty());

  // Insert an Active Directory account into AccountManager.
  UpsertActiveDirectoryAccountAndWaitForCompletion(
      ad_account_key(), kUserEmail, AccountManager::kActiveDirectoryDummyToken);

  // OAuth delegate should not return Active Directory accounts.
  EXPECT_TRUE(delegate_->GetAccounts().empty());
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       GetAccountsReturnsGaiaAccounts) {
  EXPECT_TRUE(delegate_->GetAccounts().empty());

  UpsertAccountAndWaitForCompletion(gaia_account_key(), kUserEmail, kGaiaToken);

  std::vector<CoreAccountId> accounts = delegate_->GetAccounts();
  EXPECT_EQ(1UL, accounts.size());
  EXPECT_EQ(account_info_.account_id, accounts[0]);
}

// |GetAccounts| should return all known Gaia accounts, whether or not they have
// a "valid" refresh token stored against them.
TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       GetAccountsReturnsGaiaAccountsWithInvalidTokens) {
  EXPECT_TRUE(delegate_->GetAccounts().empty());

  UpsertAccountAndWaitForCompletion(gaia_account_key(), kUserEmail,
                                    AccountManager::kInvalidToken);

  std::vector<CoreAccountId> accounts = delegate_->GetAccounts();
  EXPECT_EQ(1UL, accounts.size());
  EXPECT_EQ(account_info_.account_id, accounts[0]);
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       RefreshTokenMustBeAvailableForAllAccountsReturnedByGetAccounts) {
  EXPECT_EQ(LoadCredentialsState::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS,
            delegate_->load_credentials_state());
  EXPECT_TRUE(delegate_->GetAccounts().empty());
  const std::string kUserEmail2 = "random-email2@example.com";
  const std::string kUserEmail3 = "random-email3@example.com";

  // Insert 2 Gaia accounts and 1 Active Directory Account. Of the 2 Gaia
  // accounts, 1 has a valid refresh token and 1 has a dummy token.
  UpsertAccountAndWaitForCompletion(gaia_account_key(), kUserEmail, kGaiaToken);

  account_manager::AccountKey gaia_account_key2{
      "random-gaia-id", account_manager::AccountType::kGaia};
  account_tracker_service_.SeedAccountInfo(
      CreateAccountInfoTestFixture(gaia_account_key2.id(), kUserEmail2));
  UpsertAccountAndWaitForCompletion(gaia_account_key2, kUserEmail2,
                                    AccountManager::kInvalidToken);

  UpsertActiveDirectoryAccountAndWaitForCompletion(
      ad_account_key(), kUserEmail3,
      AccountManager::kActiveDirectoryDummyToken);

  // Verify.
  const std::vector<CoreAccountId> accounts = delegate_->GetAccounts();
  // 2 Gaia accounts should be returned.
  EXPECT_EQ(2UL, accounts.size());
  // And |RefreshTokenIsAvailable| should return true for these accounts.
  for (const CoreAccountId& account : accounts) {
    EXPECT_TRUE(delegate_->RefreshTokenIsAvailable(account));
  }
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       UpdateCredentialsSucceeds) {
  EXPECT_TRUE(delegate_->GetAccounts().empty());

  UpsertAccountAndWaitForCompletion(gaia_account_key(), account_info_.email,
                                    kGaiaToken);

  std::vector<CoreAccountId> accounts = delegate_->GetAccounts();
  EXPECT_EQ(1UL, accounts.size());
  EXPECT_EQ(account_info_.account_id, accounts[0]);
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       ObserversAreNotifiedOnAccountRemoval) {
  UpsertAccountAndWaitForCompletion(gaia_account_key(), account_info_.email,
                                    kGaiaToken);

  TestOAuth2TokenServiceObserver observer(delegate_.get());
  RemoveAccountAndWaitForCompletion(gaia_account_key());

  EXPECT_EQ(1UL, observer.batch_change_records_.size());
  EXPECT_EQ(1UL, observer.batch_change_records_[0].size());
  EXPECT_EQ(account_info_.account_id, observer.batch_change_records_[0][0]);
  EXPECT_TRUE(observer.account_ids_.empty());
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       AccountRemovedRightAfterAccountUpserted) {
  // Use StrictMock to verify that no observer methods are invoked.
  testing::StrictMock<MockProfileOAuth2TokenServiceObserver> observer(
      delegate_.get());

  // `UpsertAccount` will asynchronously send a notification through
  // `AccountManagerFacade`, so `RemoveAccount` should remove the account before
  // `ProfileOAuth2TokenServiceDelegateChromeOS` can add this account.
  account_manager_.UpsertAccount(gaia_account_key(), account_info_.email,
                                 kGaiaToken);
  account_manager_.RemoveAccount(gaia_account_key());

  task_environment_.RunUntilIdle();

  EXPECT_EQ(0UL, delegate_->GetAccounts().size());
  // Destroying the mock will verify no observer methods were called.
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       PreexistingAccountRemovedRightAfterAccountTokenUpdate) {
  UpsertAccountAndWaitForCompletion(gaia_account_key(), account_info_.email,
                                    kGaiaToken);
  EXPECT_EQ(1UL, delegate_->GetAccounts().size());

  base::RunLoop run_loop;
  MockProfileOAuth2TokenServiceObserver observer(delegate_.get());

  // Since this account already existed, `RemoveAccount` should trigger
  // `OnRefreshTokenRevoked` call to observers.
  EXPECT_CALL(observer, OnRefreshTokenRevoked(account_info_.account_id))
      .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));

  account_manager_.UpsertAccount(gaia_account_key(), account_info_.email,
                                 AccountManager::kInvalidToken);
  account_manager_.RemoveAccount(gaia_account_key());

  run_loop.Run();

  EXPECT_EQ(0UL, delegate_->GetAccounts().size());
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       SigninErrorObserversAreNotifiedOnAuthErrorChange) {
  auto error =
      GoogleServiceAuthError(GoogleServiceAuthError::State::SERVICE_ERROR);

  delegate_->UpdateAuthError(account_info_.account_id, error);

  EXPECT_EQ(error, delegate_->GetAuthError(account_info_.account_id));
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       TransientErrorsAreNotShown) {
  auto transient_error = GoogleServiceAuthError(
      GoogleServiceAuthError::State::SERVICE_UNAVAILABLE);
  EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(),
            delegate_->GetAuthError(account_info_.account_id));

  delegate_->UpdateAuthError(account_info_.account_id, transient_error);

  EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(),
            delegate_->GetAuthError(account_info_.account_id));
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       BackOffIsTriggerredForTransientErrors) {
  UpsertAccountAndWaitForCompletion(gaia_account_key(), account_info_.email,
                                    kGaiaToken);
  auto transient_error = GoogleServiceAuthError(
      GoogleServiceAuthError::State::SERVICE_UNAVAILABLE);
  delegate_->UpdateAuthError(account_info_.account_id, transient_error);
  // Add a dummy success response. The actual network call has not been made
  // yet.
  AddSuccessfulOAuthTokenResponse();

  // Transient error should repeat until backoff period expires.
  AccessTokenConsumer access_token_consumer;
  EXPECT_EQ(0, access_token_consumer.num_access_token_fetch_success_);
  EXPECT_EQ(0, access_token_consumer.num_access_token_fetch_failure_);
  std::vector<std::string> scopes{"scope"};
  std::unique_ptr<OAuth2AccessTokenFetcher> fetcher =
      delegate_->CreateAccessTokenFetcher(account_info_.account_id,
                                          delegate_->GetURLLoaderFactory(),
                                          &access_token_consumer);
  task_environment_.RunUntilIdle();
  fetcher->Start("client_id", "client_secret", scopes);
  task_environment_.RunUntilIdle();
  EXPECT_EQ(0, access_token_consumer.num_access_token_fetch_success_);
  EXPECT_EQ(1, access_token_consumer.num_access_token_fetch_failure_);
  // Expect a positive backoff time.
  EXPECT_GT(delegate_->backoff_entry_.GetTimeUntilRelease(), base::TimeDelta());

  // Pretend that backoff has expired and try again.
  delegate_->backoff_entry_.SetCustomReleaseTime(base::TimeTicks());
  fetcher = delegate_->CreateAccessTokenFetcher(
      account_info_.account_id, delegate_->GetURLLoaderFactory(),
      &access_token_consumer);
  fetcher->Start("client_id", "client_secret", scopes);
  task_environment_.RunUntilIdle();
  EXPECT_EQ(1, access_token_consumer.num_access_token_fetch_success_);
  EXPECT_EQ(1, access_token_consumer.num_access_token_fetch_failure_);
}

TEST_F(ProfileOAuth2TokenServiceDelegateChromeOSTest,
       BackOffIsResetOnNetworkChange) {
  UpsertAccountAndWaitForCompletion(gaia_account_key(), account_info_.email,
                                    kGaiaToken);
  auto transient_error = GoogleServiceAuthError(
      GoogleServiceAuthError::State::SERVICE_UNAVAILABLE);
  delegate_->UpdateAuthError(account_info_.account_id, transient_error);
  // Add a dummy success response. The actual network call has not been made
  // yet.
  AddSuccessfulOAuthTokenResponse();

  // Transient error should repeat until backoff period expires.
  AccessTokenConsumer access_token_consumer;
  EXPECT_EQ(0, access_token_consumer.num_access_token_fetch_success_);
  EXPECT_EQ(0, access_token_consumer.num_access_token_fetch_failure_);
  std::vector<std::string> scopes{"scope"};
  std::unique_ptr<OAuth2AccessTokenFetcher> fetcher =
      delegate_->CreateAccessTokenFetcher(account_info_.account_id,
                                          delegate_->GetURLLoaderFactory(),
                                          &access_token_consumer);
  task_environment_.RunUntilIdle();
  fetcher->Start("client_id", "client_secret", scopes);
  task_environment_.RunUntilIdle();
  EXPECT_EQ(0, access_token_consumer.num_access_token_fetch_success_);
  EXPECT_EQ(1, access_token_consumer.num_access_token_fetch_failure_);
  // Expect a positive backoff time.
  EXPECT_GT(delegate_->backoff_entry_.GetTimeUntilRelease(), base::TimeDelta());

  // Notify of network change and ensure that request now runs.
  delegate_->OnConnectionChanged(
      network::mojom::ConnectionType::CONNECTION_WIFI);
  fetcher = delegate_->CreateAccessTokenFetcher(
      account_info_.account_id, delegate_->GetURLLoaderFactory(),
      &access_token_consumer);
  fetcher->Start("client_id", "client_secret", scopes);
  task_environment_.RunUntilIdle();
  EXPECT_EQ(1, access_token_consumer.num_access_token_fetch_success_);
  EXPECT_EQ(1, access_token_consumer.num_access_token_fetch_failure_);
}

}  // namespace signin
